﻿using System;
using System.Collections.Generic;

using System.Text;
using System.Collections;

using System.IO;
using System.Xml;
using System.Xml.XPath;
using System.Text.RegularExpressions;
using System.Web;

using System.Xml.Xsl;
using System.Xml.Schema;

using System.Diagnostics;
using System.Reflection;


namespace SchematronTestFramework
{
    class TestCommand
    {
        public string filepath;
        public XPathDocument docNav;
        public XmlDocument xmldoc;

        public List<XmlNode> nodes = new List<XmlNode>();
        public string assert_str;

        public string result = "";
        public string expression;
        public string additional_exp;
        public string errorText;
        public string fullassert;

        public string testexpr = "";
        public string ruleexpr = "";
        public string workingfolder = "";

        public static int passed = 0;
        public static int fn = 0;
        public static int fp = 0;
        public static int error = 0;
        public static int notrun = 0;

        public string report = "";
        static List<string> functionnames = new List<string>();

        public bool isToRunAssert = true;
        public string schematron = "";
        public int getLineNumber(string msg)
        {
            StreamReader sr = new StreamReader(schematron);

            //Console.WriteLine(":::: " + testexpr);
            string lines = sr.ReadToEnd();
            msg = msg.Trim();
            //Console.WriteLine(msg);

            int lineno = 0;
            if (lines.IndexOf(ruleexpr) > 0)
            {
                int ruleindex = lines.IndexOf(ruleexpr);
                if (lines.IndexOf(testexpr, ruleindex) > 0)
                {
                    int start = lines.IndexOf(testexpr, ruleindex);

                    if (lines.IndexOf(msg, start) > 0)
                    {
                        int index = lines.IndexOf(msg, start);
                        while (index >= 0)
                        {
                            if (lines[index] == '\n')
                            {
                                lineno++;
                            }
                            index = index - 1;

                        }


                    }
                }
            }
            return lineno + 1;
        }

        public TestCommand(string file, string expr, string add_exp, string ass, string error, string full, string rep, string schema)
        {
            if (functionnames.IndexOf("not") < 0)
            {
                functionnames.Add("not");
            }
            if (functionnames.IndexOf("translate") < 0)
            {
                functionnames.Add("translate");
            }
            if (functionnames.IndexOf("normalize-space") < 0)
            {
                functionnames.Add("normalize-space");
            }
            if (functionnames.IndexOf("floor") < 0)
            {
                functionnames.Add("floor");
            }
            if (functionnames.IndexOf("number") < 0)
            {
                functionnames.Add("number");
            }
            if (functionnames.IndexOf("substring-before") < 0)
            {
                functionnames.Add("substring-before");
            }
            if (functionnames.IndexOf("substring-after") < 0)
            {
                functionnames.Add("substring-after");
            }
            if (functionnames.IndexOf("contains") < 0)
            {
                functionnames.Add("contains");
            }
            if (functionnames.IndexOf("count") < 0)
            {
                functionnames.Add("count");
            }


            testexpr = add_exp;
            ruleexpr = expr;
            report = rep;

            schematron = schema;
            filepath = file;
            workingfolder = System.IO.Path.GetDirectoryName(filepath);

            workingfolder = workingfolder + "\\" + System.Guid.NewGuid().ToString().Substring(0, 10);



            System.IO.Directory.CreateDirectory(workingfolder);
            expression = expr;
            additional_exp = add_exp;
            assert_str = ass;

            errorText = error;
            fullassert = full;
            docNav = new XPathDocument(filepath);
            xmldoc = new XmlDocument();
            xmldoc.Load(filepath);


        }


        public string takeOffPredicate(string target)
        {
            target = target.Trim();
            if (target.Contains("["))
            {
                string name = target.Substring(0, target.IndexOf("["));

                target = target.Substring(target.IndexOf("[") + 1, target.LastIndexOf("]") - target.IndexOf("[") - 1);

                target = takeOffFunctions(target);

                if (target.StartsWith("["))
                {
                    target = name + target;
                }
                else
                {
                    target = name + "[" + target + "]";
                }

            }

            return target;
        }

        public string takeOffFunctions(string target)
        {
            target = target.Trim();
            if (target.Contains("(") && target.Contains(")"))
            {
                int index = target.IndexOf("(");

                int start = index - 1;
                while (start >= 0)
                {
                    if ((target[start] >= 'a' && target[start] <= 'z') || (target[start] >= 'A' && target[start] <= 'Z') || (target[start] >= '0' && target[start] <= '9') || target[start] == '-')
                    {
                        start = start - 1;
                    }
                    else
                    {
                        break;
                    }
                }

                start = start + 1;

                string name = target.Substring(start, index - start).Trim();

                if (target.Contains("[") && (start > target.IndexOf("[")) && target.Contains("]") && (start < target.IndexOf("]")))
                {
                    return target;
                }
                else
                {
                    
                    if (functionnames.IndexOf(name) >= 0)
                    {
                        target = target.Remove(start, index - start + 1);
                        target = target.Remove(target.LastIndexOf(")"), target.Length - target.LastIndexOf(")"));
                        //target = target.Substring(target.IndexOf("(") + 1, target.LastIndexOf(")") - target.IndexOf("(") - 1);
                        target = takeOffFunctions(target);
                    }
                }
            }
            if (target.Contains(","))
            {
                if (target.Contains("[") && (target.IndexOf(",") > target.IndexOf("[")) && target.Contains("]") && (target.IndexOf(",") < target.IndexOf("]")))
                {
                    return target;
                }
                else
                {
                    int indexd = target.IndexOf(",");

                    target = target.Substring(0, target.IndexOf(","));

                    if (target.Contains("["))
                    {
                        if (target.IndexOf("[") < indexd)
                        {
                            target = target + "]";
                        }
                    }
                }
            }
            if (target.Contains("[") && (!(target.Contains("]"))))
            {
                target = target + "]";
            }
            if (target.Trim().StartsWith("@"))
            {
                target = target.Trim();
                target = "[" + target + "]";
            }
            return target;

        }

        public string doTransformation(string schfile, string tovalidate)
        {
            var commandlines = new string[2];
            string workingdic = System.IO.Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
            string xalanpath = "\"" + workingdic + "\\xalan.jar" + "\"";
            string skeleton = "\"" + workingdic + "\\skeleton1-5.xsl" + "\"";
            commandlines[0] = "java -jar " + xalanpath + " -in " + "\"" + schfile + "\"" + " -xsl " + skeleton + " -out " + "\"" + workingfolder + "/temp_sch.sch" + "\"";
            commandlines[1] = "java -jar " + xalanpath + " -in " + "\"" + tovalidate + "\"" + " -xsl " + "\"" + workingfolder + "/temp_sch.sch" + "\"" + " -out " + "\"" + workingfolder + "/result_sch.xml" + "\"";
            string runbat = workingfolder + "\\run.bat";
            File.WriteAllLines(runbat, commandlines);

            // Use ProcessStartInfo class
            var startInfo = new ProcessStartInfo();
            startInfo.CreateNoWindow = true;
            startInfo.UseShellExecute = false;
            startInfo.FileName = workingfolder + "/run.bat";
            startInfo.WindowStyle = ProcessWindowStyle.Hidden;
            startInfo.RedirectStandardOutput = true;


            try
            {
                // Start the process with the info we specified.
                // Call WaitForExit and then the using statement will close.
                using (Process exeProcess = Process.Start(startInfo))
                {
                    exeProcess.WaitForExit();
                    using (StreamReader reader = exeProcess.StandardOutput)
                    {
                        result = reader.ReadToEnd();
                    }
                }
            }
            catch (Exception ex)
            {
                result = ex.Message;
            }


            StreamReader sr = new StreamReader(workingfolder + "/result_sch.xml", System.Text.Encoding.Default);
            string res = sr.ReadToEnd();
            sr.Close();
            //Console.WriteLine("--------------Result: "+res);
            return res;
        }

        virtual public void analyzeErrorText() { }

        public string getSelectionExpr(string expression)
        {
            if (additional_exp.Trim().Contains("document") && additional_exp.Trim().Contains("("))
            {

                int index1 = additional_exp.LastIndexOf("=");
                int index2 = additional_exp.LastIndexOf("@");

                int index3 = additional_exp.LastIndexOf(":");

                int end = 0;

                int start = 0;
                if (index2 > 0 && index2 > index1)
                {
                    end = (index2) + 1;
                    while (end < additional_exp.Length - 1)
                    {
                        if ((additional_exp[end] >= 'a' && additional_exp[end] <= 'z') || (additional_exp[end] >= 'A' && additional_exp[end] <= 'Z') || (additional_exp[end] >= '0' && additional_exp[end] <= '9') || (additional_exp[end] == '/') || (additional_exp[end] == '@') || (additional_exp[end] == ':'))
                        {
                            end = end + 1;
                        }
                        else
                        {
                            break;
                        }
                    }
                    end = end - 1;

                    start = (index2) - 1;
                    while (start >= 0)
                    {
                        if ((additional_exp[start] >= 'a' && additional_exp[start] <= 'z') || (additional_exp[start] >= 'A' && additional_exp[start] <= 'Z') || (additional_exp[start] >= '0' && additional_exp[start] <= '9') || (additional_exp[start] == '/') || (additional_exp[start] == '@') || (additional_exp[start] == ':'))
                        {
                            start = start - 1;
                        }
                        else
                        {
                            break;
                        }
                    }
                    start = start + 1;
                }
                else if (index3 > 0 && index3 > index1)
                {
                    end = (index3) + 1;
                    while (end < additional_exp.Length - 1)
                    {
                        if ((additional_exp[end] >= 'a' && additional_exp[end] <= 'z') || (additional_exp[end] >= 'A' && additional_exp[end] <= 'Z') || (additional_exp[end] >= '0' && additional_exp[end] <= '9') || (additional_exp[end] == '/') || (additional_exp[end] == '@') || (additional_exp[end] == ':'))
                        {
                            end = end + 1;
                        }
                        else
                        {
                            break;
                        }
                    }
                    end = end - 1;

                    start = (index3) - 1;
                    while (start >= 0)
                    {
                        if ((additional_exp[start] > 'a' && additional_exp[start] < 'z') || (additional_exp[start] > 'A' && additional_exp[start] < 'Z') || (additional_exp[start] > '0' && additional_exp[start] < '9') || (additional_exp[start] == '/') || (additional_exp[start] == '@') || (additional_exp[start] == ':'))
                        {
                            start = start - 1;
                        }
                        else
                        {
                            break;
                        }
                    }
                    start = start + 1;
                }

                if (start != 0 && end != 0)
                {
                    string res = additional_exp.Substring(start, end - start + 1);

                    res = res.Trim();

                    if (res.StartsWith("/@"))
                    {
                        res = res.Substring(1, res.Length - 1);
                    }
                    additional_exp = additional_exp.Replace(additional_exp.Substring(additional_exp.IndexOf("document"), additional_exp.LastIndexOf("]") - additional_exp.IndexOf("document") + 1), res);

                }

            }

            if (additional_exp.Contains(" or"))
            {

                string[] exps = additional_exp.Split(new string[] { " or" }, StringSplitOptions.None);
                additional_exp = exps[0];
                //Console.WriteLine("Split is: " + exps[0]);
            }
            else if (additional_exp.Contains(" and"))
            {

                string[] exps = additional_exp.Split(new string[] { " and" }, StringSplitOptions.None);
                additional_exp = exps[0];
                //
                //Console.WriteLine("Split is: " + exps[0]);
            }

            additional_exp = additional_exp.Trim();
            additional_exp = takeOffFunctions(additional_exp);

            if (!(additional_exp.Contains("[")) && !(additional_exp.Contains("]")) && (additional_exp.Contains("!=") || additional_exp.Contains("=")))
            {
                if (additional_exp.Contains("!="))
                {
                    additional_exp = additional_exp.Substring(0, additional_exp.IndexOf("!="));
                    additional_exp = additional_exp.Trim();
                }
                else
                {
                    additional_exp = additional_exp.Substring(0, additional_exp.IndexOf("="));
                    additional_exp = additional_exp.Trim();
                }
            }

            if (!(additional_exp.Contains("[")) && additional_exp.Contains("/") && (additional_exp.Contains("=") || additional_exp.Contains("!=")))
            {
                int index = additional_exp.IndexOf("=");
                if (index <= 0)
                {
                    index = additional_exp.IndexOf("!=");
                }

                int start = index - 1;
                while (start >= 0)
                {
                    if (additional_exp[start] != '/')
                    {
                        start = start - 1;
                    }
                    else
                    {
                        break;
                    }
                }

                additional_exp = additional_exp.Remove(start, 1);
                additional_exp = additional_exp.Insert(start, "[");
                additional_exp = additional_exp + "]";

            }
            if (additional_exp.StartsWith("/"))
            {
                expression = additional_exp;
            }
            else if (additional_exp.StartsWith("["))
            {
                expression = expression + additional_exp;
            }
            else
            {
                if (expression.EndsWith("/"))
                {
                    expression.Remove(expression.LastIndexOf('/'), 1);

                }
                expression = expression + "/" + additional_exp;
            }
            if (!(expression.Trim().StartsWith("/")))
            {
                expression = "descendant::" + expression;
            }

            return expression;
            //nodeIter = nav.Select(expression);
        }
        public void selectNodes(string expression)
        {
            XmlNodeList nodeList;
            XmlNode root = xmldoc.DocumentElement;

            //Console.WriteLine("expression used to match nodes:" + expression);


            XmlNamespaceManager nm = new XmlNamespaceManager(xmldoc.NameTable);
            nm.AddNamespace("cda", "urn:hl7-org:v3");
            nm.AddNamespace("ext", "http://ns.electronichealth.net.au/Ci/Cda/Extensions/3.0");
            nm.AddNamespace("xs", "http://www.w3.org/2001/XMLSchema");
            nm.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
            try
            {
                nodeList = root.SelectNodes(expression, nm);
                foreach (XmlNode node in nodeList)
                {
                    //Console.WriteLine("matched node by expression: ", nodeIter.Current.Value);
                    //nodeIter.Current.
                    //Console.WriteLine("node:  " + node.Name);
                    nodes.Add(node);

                }
                if (nodes.Count <= 0)
                {
                    isToRunAssert = false;
                    result = "error";

                    error++;


                    Console.WriteLine("============= Error in rule context or assert test statement ===========\n" +
                        "File: " + schematron + "\n" +
                        "Line: " + getLineNumber(errorText) + "\n" +
                        "Advice: please double check the rule context and assert test statement;\n" +
                        "        another reason for this error is the tag or attribute is optional.\n" +
                        "************************************************************************\n\n");


                    //Console.WriteLine("Failed: Error in rule context and assert test statement\n\n\n");

                }
            }
            catch (Exception e)
            {
                Console.WriteLine("********* Error: " + e.Message + " *********");
            }
        }
        public virtual void getMatchedNodes()
        {

            if (expression.Contains("|"))
            {
                string[] expressions = expression.Split('|');

                foreach (string str in expressions)
                {
                    string selec = getSelectionExpr(str);
                    selectNodes(selec);
                }
            }
            else
            {
                XmlNamespaceManager nm = new XmlNamespaceManager(xmldoc.NameTable);
                nm.AddNamespace("cda", "urn:hl7-org:v3");
                nm.AddNamespace("ext", "http://ns.electronichealth.net.au/Ci/Cda/Extensions/3.0");
                nm.AddNamespace("xs", "http://www.w3.org/2001/XMLSchema");
                nm.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
                XPathNavigator nav = xmldoc.CreateNavigator();
                XPathNodeIterator nodes = nav.Select(expression,nm);

                foreach (XPathNavigator node in nodes)
                {
                    object eval = node.Evaluate(additional_exp,nm);

                    if (eval != null)
                    {
                        Console.WriteLine("888888888888888888888888888888888");
                    }
                }
                string selec = getSelectionExpr(expression);
                selectNodes(selec);
            }



        }
        virtual public void updateXML() { }
        virtual public void runAssert()
        {

            using (StreamWriter sw = new StreamWriter(workingfolder + "\\tempschematron.sch"))
            {
                //Console.Write(fullassert);
                sw.Write(fullassert);
            }
            result = doTransformation(workingfolder + "\\tempschematron.sch", workingfolder + "\\testtemp.xml");


        }
        virtual public void analyzeResult()
        {

            //System.IO.Directory.Delete(workingfolder, true);

        }

        virtual public void copyXml()
        {
            File.Copy(filepath, workingfolder + "/testtemp.xml");
            string dic = System.IO.Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
            string path = dic + "/Vocabs";
            string[] xmlfiles = System.IO.Directory.GetFiles(path);

            foreach (string file in xmlfiles)
            {
                File.Copy(file, workingfolder + "/" + System.IO.Path.GetFileName(file));
            }
        }
        public void analyzeFN()
        {
            string resultxml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";

            if (result.Length > resultxml.Length)
            {
                fp++;


                Reporter reporter = new Reporter(report);

                reporter.CreateMessage(schematron, getLineNumber(errorText), "False Positive", assert_str);

                Console.WriteLine("======================== False Positive ================================\n" +
                    "File: " + filepath + "\n" +
                    "Line: " + getLineNumber(errorText) + "\n" +
                    "************************************************************************\n\n");


            }

            File.Delete(workingfolder + "/run.bat");
            File.Delete(workingfolder + "/tempschematron.sch");
            File.Delete(workingfolder + "/testtemp.xml");
            File.Delete(workingfolder + "/result_sch.xml");
            File.Delete(workingfolder + "/temp_sch.sch");
        }
        virtual public void run()
        {

            //Console.WriteLine(fullassert);
            analyzeErrorText();
            getMatchedNodes();




            if (isToRunAssert)
            {
                copyXml();
                runAssert();
                analyzeFN();

                updateXML();
                runAssert();
                analyzeResult();
            }

            System.IO.Directory.Delete(workingfolder, true);



        }

    }
}
